package com.liuzhilong.infrastructure.middleware.elasticsearch.infastructure.book;

import com.google.common.base.Preconditions;
import com.liuzhilong.infrastructure.middleware.elasticsearch.entity.Book;
import com.liuzhilong.infrastructure.middleware.elasticsearch.infastructure.book.assembler.BookESAssembler;
import com.liuzhilong.infrastructure.middleware.elasticsearch.repository.BookRepository;
import lombok.AccessLevel;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.lucene.util.QueryBuilder;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetRequestBuilder;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.builder.SearchSourceBuilder;

import javax.management.Query;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

public class EsBookRepository implements BookRepository, AutoCloseable {
    @Setter
    @Accessors(chain = true)
    private String indexName;

    @Setter(AccessLevel.PRIVATE)
    @Accessors(chain = true)
    private RestHighLevelClient client;

    public static final EsBookRepository create(String indexName) {
        Preconditions.checkNotNull(indexName);
        //设置认证
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "elastic,pwd"));

        RestClientBuilder builder = RestClient.builder(
                new HttpHost("10.138.23.26", 9200, "http"),
                new HttpHost("10.138.23.26", 9200, "http"))
                .setRequestConfigCallback(requestConfigBuilder -> {
                    requestConfigBuilder.setConnectTimeout(-1);
                    requestConfigBuilder.setSocketTimeout(-1);
                    requestConfigBuilder.setConnectionRequestTimeout(-1);
                    return requestConfigBuilder;
                }).setHttpClientConfigCallback(httpClientBuilder -> {
                    httpClientBuilder.disableAuthCaching();
                    return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                });
        RestHighLevelClient client = new RestHighLevelClient(
                builder);
        return new EsBookRepository().setClient(client).setIndexName(indexName);
    }


    /**
     * 创建一个新的book，并返回id信息
     *
     * @param book
     * @return
     */
    @Override
    public String newBook(Book book) {
        Preconditions.checkNotNull(book);
        Preconditions.checkArgument(StringUtils.isBlank(book.getId()));
        String newId = UUID.randomUUID().toString();
        book.setId(newId);
        saveElastic(book);
        return newId;
    }

    private void saveElastic(Book book) {
        IndexRequest indexRequest = new IndexRequest(this.indexName)
                //此处还需要设置type，其实type已经不需要了，但是6.X版本还需要
                //创建index的api已经做了修改不需要设置index，事实上6.x默认设置type名为 '_doc' 这里还没改过
                .type("_doc")
                .id(book.getId())
                .source(BookESAssembler.bookToJson(book), XContentType.JSON)
                // 设置routing ,routing 相同的数据会放在同一个分片上，使用routing可以缩小要检索的范围，加快检索的速度
//                .routing(book.getName())

                //主分片处理完毕的时间
            .timeout(TimeValue.timeValueSeconds(1))

                // 设置版本号由外部提供，可以设置版本号。只有版本号大于当前es中记录的该文档的版本号才能保存成功
                // 主要用于乐观锁的实现
//                .versionType(VersionType.EXTERNAL)
//                .version(1L)
                ;


        try {
            IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
            System.out.println(indexResponse.getId());
            return;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }

    /**
     * 更新，根据id信息
     *
     * @param book
     */
    @SneakyThrows
    @Override
    public void update(Book book) {
        UpdateRequest updateRequest = new UpdateRequest().index(this.indexName).type("_doc").id(book.getId()).doc(BookESAssembler.bookToJson(book), XContentType.JSON);

// 如果创建的时候指定了routing，更新的时候也要写上。
//        updateRequest.routing("routing");
        UpdateResponse updateResponse= client.update(updateRequest,RequestOptions.DEFAULT);
    }

    /**
     * 根据id获取
     *
     * @param id
     * @return
     */
    @SneakyThrows
    @Override
    public Book load(String id) {

        GetRequest getRequest = new GetRequest().index(this.indexName).type("_doc").id(id);

        GetResponse getResponse=client.get(getRequest,RequestOptions.DEFAULT);
        return BookESAssembler.jsonToBook( getResponse.getSourceAsString());
    }

    //查询功能
    @SneakyThrows
    @Override
    public List<Book> search(Book book) {

        SearchRequest searchRequest = new SearchRequest().indices(this.indexName).types("_doc");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder top = QueryBuilders.boolQuery();
        top.must(QueryBuilders.matchQuery("index",book.getIndex()));
        if(StringUtils.isNotBlank(book.getSummary())){
            top.must(QueryBuilders.matchQuery("summary", book.getSummary()));
        }

        if(StringUtils.isNotBlank(book.getName())||(book.getAuthors()!=null&&book.getAuthors().size()>0)){

            BoolQueryBuilder innner = QueryBuilders.boolQuery();
            if(StringUtils.isNotBlank(book.getName())){
                innner.should(QueryBuilders.termQuery("name",book.getName()));
            }
            if(book.getAuthors()!=null&&book.getAuthors().size()>0){
                innner.should(QueryBuilders.termsQuery("author", book.getAuthors()));
            }

        }

        searchSourceBuilder
                .query(top);
        searchRequest.source(searchSourceBuilder);

      SearchResponse response= client.search(searchRequest,RequestOptions.DEFAULT);

        return Arrays.stream(response.getHits().getHits())
                .map(item->{
                    System.out.println(item.getScore());
                    return item;
                })
                .map(item->item.getSourceAsString())
                .map(BookESAssembler::jsonToBook)
                .collect(Collectors.toList());





    }

    @Override
    public List<Book> scroll(Book book, int size) {
        return null;
    }

    @Override
    public List<Book> scroll(String scrollId, String nextId, int size) {
        return null;
    }

    @Override
    public void close() {

        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建索引
     */
    @SneakyThrows
    private void createIndex() {


        CreateIndexRequest createIndexRequest = new CreateIndexRequest(this.indexName);
        Settings settings = Settings.builder()
                //三个分片
                .put("index.number_of_shards", 3)
                //每个分片设置两个备份
                .put("index.number_of_replicas", 2)
                .build();
        createIndexRequest.settings(settings);

        String mapping = IOUtils.toString(this.getClass().getClassLoader().getResourceAsStream("book_property.json"), "UTF-8");
        createIndexRequest.mapping(mapping, XContentType.JSON);
        //设置所有节点建立的超时时间
        createIndexRequest.setTimeout(TimeValue.timeValueMinutes(2));
        createIndexRequest.setMasterTimeout(TimeValue.timeValueMinutes(1));
        CreateIndexResponse createIndexResponse = client.indices()
                .create(createIndexRequest, RequestOptions.DEFAULT);
        System.out.println("创建成功");
        System.out.println(createIndexResponse.index());
    }

    /**
     * 删除
     */
    @SneakyThrows
    public void destroyIndex() {
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(this.indexName);
        client.indices()
                .delete(deleteIndexRequest, RequestOptions.DEFAULT);

    }


    public EsBookRepository initIndex() {
        this.createIndex();
        return this;
    }


}
